package com.gitee.wsl.android.ui.crosstabdnd

//package it.niedermann.android.crosstabdnd;

import android.content.res.Resources
import android.util.Log
import android.view.DragEvent
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.gitee.wsl.android.ui.crosstabdnd.DragAndDropUtil.getTabFragment
import com.google.android.material.tabs.TabLayout
import timber.log.Timber
import java.util.Objects

class CrossTabDragAndDrop<TabFragment, ItemAdapter, ItemModel : DragAndDropModel>(
    resources: Resources,
    val isLayoutLtr: Boolean = false,
    val dragAndDropMsToReactTopBottom: Int = 100,
    val dragAndDropMsToReact: Int = 500
) where TabFragment : Fragment, TabFragment : DragAndDropTab<ItemAdapter>, ItemAdapter : RecyclerView.Adapter<*>, ItemAdapter : DragAndDropAdapter<ItemModel> {
     val pxToReact: Float
     val pxToReactTopBottom: Float

     val displayX: Int
     var lastSwap: Long = 0
     var lastMove: Long = 0
     val moveListenerList: MutableSet<ItemMovedByDragListener<ItemModel>> = HashSet(1)

    init {
        displayX = resources.displayMetrics.widthPixels
        val density = resources.displayMetrics.density
        pxToReact = 30 * density
        pxToReactTopBottom = dragAndDropMsToReactTopBottom * density

    }

    fun register(viewPager: ViewPager2, stackLayout: TabLayout, fm: FragmentManager) {
        viewPager.setOnDragListener { _: View?, dragEvent: DragEvent ->
            val draggedItemLocalState =
                dragEvent.localState as DraggedItemLocalState<TabFragment, ItemAdapter, ItemModel>
            // https://github.com/stefan-niedermann/nextcloud-deck/issues/1025
            if (draggedItemLocalState == null) {
                Timber.v("dragEvent has no localState → Cancelling DragListener.")
                return@setOnDragListener false
            }

            val draggedView = draggedItemLocalState.draggedView
            when (dragEvent.action) {
                DragEvent.ACTION_DRAG_STARTED -> {
                    draggedView.visibility = View.INVISIBLE
                    draggedItemLocalState.onDragStart(viewPager, fm)
                }

                DragEvent.ACTION_DRAG_LOCATION -> {
                    val currentRecyclerView = draggedItemLocalState.recyclerView?:return@setOnDragListener false
                    val itemAdapter = draggedItemLocalState.itemAdapter
                    val now = System.currentTimeMillis()
                    if (lastSwap + dragAndDropMsToReact < now) { // don't change Tabs so fast!
                        val oldTabPosition = viewPager.currentItem
                        var shouldSwitchTab = true
                        var newTabPosition = -1
                        // change tab? if yes, which direction?
                        if (dragEvent.x <= pxToReact) {
                            newTabPosition = if (isLayoutLtr) oldTabPosition - 1 else oldTabPosition + 1
                        } else if (dragEvent.x >= displayX - pxToReact) {
                            newTabPosition = if (isLayoutLtr) oldTabPosition + 1 else oldTabPosition - 1
                        } else {
                            shouldSwitchTab = false
                        }
                        if (shouldSwitchTab && isMovePossible(viewPager, newTabPosition)) {
                            removeItem(currentRecyclerView, draggedView, itemAdapter)
                            detectAndKillDuplicatesInNeighbourTab(
                                viewPager,
                                draggedItemLocalState.draggedItemModel,
                                fm,
                                oldTabPosition,
                                newTabPosition
                            )
                            switchTab(
                                dragEvent,
                                viewPager,
                                stackLayout,
                                fm,
                                draggedItemLocalState,
                                now,
                                newTabPosition
                            )
                            return@setOnDragListener true
                        }
                    }

                    //scroll if needed
                    if (dragEvent.y <= pxToReactTopBottom) {
                        SCROLL_HELPER.startScroll(
                            currentRecyclerView,
                            ScrollHelper.ScrollDirection.UP
                        )
                    } else if (dragEvent.y >= currentRecyclerView.bottom - pxToReactTopBottom) {
                        SCROLL_HELPER.startScroll(
                            currentRecyclerView,
                            ScrollHelper.ScrollDirection.DOWN
                        )
                    } else {
                        SCROLL_HELPER.stopScroll()
                    }
                    if (lastMove + dragAndDropMsToReactTopBottom < now) {
                        //push around the other items
                        pushAroundItems(
                            draggedView,
                            currentRecyclerView,
                            dragEvent,
                            itemAdapter,
                            draggedItemLocalState,
                            now
                        )
                    }
                }

                DragEvent.ACTION_DRAG_ENDED -> {
                    draggedItemLocalState.recyclerView.removeOnChildAttachStateChangeListener(
                        draggedItemLocalState.insertedListener!!
                    )
                    SCROLL_HELPER.stopScroll()
                    draggedView.visibility = View.VISIBLE
                    // Clean up the original dragged view, so the next onBindViewHolder() will not display the view at the position of the original dragged view as View.INVISIBLE
                    draggedItemLocalState.originalDraggedView.visibility = View.VISIBLE
                    notifyListeners(draggedItemLocalState)
                }
            }
            true
        }
    }

    private fun switchTab(
        dragEvent: DragEvent,
        viewPager: ViewPager2,
        stackLayout: TabLayout,
        fm: FragmentManager,
        draggedItemLocalState: DraggedItemLocalState<TabFragment, ItemAdapter, ItemModel>,
        now: Long,
        newPosition: Int
    ) {
        viewPager.currentItem = newPosition
        draggedItemLocalState.onTabChanged(viewPager, fm)
        stackLayout.getTabAt(newPosition)?.select()?:return
        val recyclerView = draggedItemLocalState.recyclerView
        val itemAdapter = draggedItemLocalState.itemAdapter
        val onChildAttachStateChangeListener: RecyclerView.OnChildAttachStateChangeListener =
            object : RecyclerView.OnChildAttachStateChangeListener {
                override fun onChildViewAttachedToWindow(view: View) {
                    recyclerView!!.removeOnChildAttachStateChangeListener(this)
                    draggedItemLocalState.insertedListener = null
                    view.visibility = View.INVISIBLE
                    draggedItemLocalState.draggedView = view
                    pushAroundItems(
                        view,
                        recyclerView,
                        dragEvent,
                        itemAdapter,
                        draggedItemLocalState,
                        now
                    )
                }

                override fun onChildViewDetachedFromWindow(view: View) { /* do nothing */
                }
            }
        draggedItemLocalState.insertedListener = onChildAttachStateChangeListener
        recyclerView!!.addOnChildAttachStateChangeListener(onChildAttachStateChangeListener)

        //insert item in new tab
        val firstVisibleView = recyclerView.getChildAt(0)
        val positionToInsert =
            if (firstVisibleView == null) 0 else recyclerView.getChildAdapterPosition(
                firstVisibleView
            ) + 1
        itemAdapter.insertItem(draggedItemLocalState.draggedItemModel, positionToInsert)
        lastSwap = now
    }

    private fun pushAroundItems(
        view: View,
        recyclerView: RecyclerView?,
        dragEvent: DragEvent,
        itemAdapter: ItemAdapter,
        draggedItemLocalState: DraggedItemLocalState<TabFragment, ItemAdapter, ItemModel>,
        now: Long
    ) {
        val viewUnder = recyclerView!!.findChildViewUnder(dragEvent.x, dragEvent.y)
        if (viewUnder != null) {
            val toPositon = recyclerView.getChildAdapterPosition(viewUnder)
            if (toPositon != -1) {
                val fromPosition = recyclerView.getChildAdapterPosition(view)
                if (fromPosition != -1 && fromPosition != toPositon) {
                    recyclerView.post {
                        itemAdapter.moveItem(fromPosition, toPositon)
                        draggedItemLocalState.positionInItemAdapter = toPositon
                    }
                    lastMove = now
                }
            }
        }
    }

    private fun detectAndKillDuplicatesInNeighbourTab(
        viewPager: ViewPager2,
        itemToFind: ItemModel,
        fm: FragmentManager,
        oldTabPosition: Int,
        newTabPosition: Int
    ) {
        val tabPositionToCheck =
            if (newTabPosition > oldTabPosition) newTabPosition + 1 else newTabPosition - 1
        if (isMovePossible(viewPager, tabPositionToCheck)) {
            viewPager.registerOnPageChangeCallback(object : OnPageChangeCallback() {
                override fun onPageSelected(position: Int) {
                    super.onPageSelected(position)
                    viewPager.unregisterOnPageChangeCallback(this)
                    val itemAdapter = getTabFragment<TabFragment>(
                        fm,
                        Objects.requireNonNull(viewPager.adapter).getItemId(tabPositionToCheck)
                    )!!.adapter
                    itemAdapter.registerAdapterDataObserver(object :
                        RecyclerView.AdapterDataObserver() {
                        override fun onChanged() {
                            super.onChanged()
                            itemAdapter.unregisterAdapterDataObserver(this)
                            val itemList = itemAdapter.itemList
                            for (i in itemList.indices) {
                                val c = itemList[i]
                                if (itemToFind.comparableId == c.comparableId) {
                                    itemAdapter.removeItem(i)
                                    itemAdapter.notifyItemRemoved(i)
                                    Timber.v("DnD removed dupe at tab $tabPositionToCheck: $c")
                                    break
                                }
                            }
                        }
                    })
                }
            })
        }
    }

    private fun removeItem(
        currentRecyclerView: RecyclerView,
        view: View,
        itemAdapter: ItemAdapter
    ) {
        val oldItemPosition = currentRecyclerView.getChildAdapterPosition(view)
        if (oldItemPosition != -1) {
            itemAdapter.removeItem(oldItemPosition)
        }
    }

    private fun notifyListeners(draggedItemLocalState: DraggedItemLocalState<TabFragment, ItemAdapter, ItemModel>) {
        for (listener in moveListenerList) {
            listener.onItemMoved(
                draggedItemLocalState.draggedItemModel,
                draggedItemLocalState.currentTabId,
                draggedItemLocalState.positionInItemAdapter
            )
        }
    }

    fun addItemMovedByDragListener(listener: ItemMovedByDragListener<ItemModel>) {
        moveListenerList.add(listener)
    }

    companion object {
        private val SCROLL_HELPER = ScrollHelper()
        private fun isMovePossible(viewPager: ViewPager2, newPosition: Int): Boolean {
            return newPosition < Objects.requireNonNull(viewPager.adapter).itemCount && newPosition >= 0
        }
    }
}